package org.zoomdev.zoom.dao.impl;

import org.apache.commons.lang3.StringUtils;
import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.expression.Symbol;
import org.zoomdev.zoom.common.filter.Filter;
import org.zoomdev.zoom.common.io.Io;
import org.zoomdev.zoom.common.utils.Page;
import org.zoomdev.zoom.dao.Ar;
import org.zoomdev.zoom.dao.ConnectionExecutor;
import org.zoomdev.zoom.dao.Dao;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.Entity;
import org.zoomdev.zoom.dao.Select;
import org.zoomdev.zoom.dao.Sql;
import org.zoomdev.zoom.dao.SqlBuilder;
import org.zoomdev.zoom.dao.adapters.EntityField;
import org.zoomdev.zoom.dao.adapters.ResultSetAdapter;
import org.zoomdev.zoom.dao.adapters.result.ResultSetAdapters;
import org.zoomdev.zoom.dao.driver.SqlDriver;
import org.zoomdev.zoom.dao.transaction.Transactions;
import org.zoomdev.zoom.dao.utils.DaoUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;


public class ArImpl<T> implements Ar<T> {


    public static final boolean DEFAULT_IGNORE_ENTITY_CONDITION = false;

    public static boolean DEFAULT_IGNORE_NULL = true;
    public static boolean DEFAULT_STRICT = true;
    protected final DataSource ds;
    private final Transactions transactions;
    private final SqlDriver driver;
    private final SimpleSqlBuilder builder;
    private final Dao dao;
    protected Entity entity;
    protected Filter<EntityField> filter;

    //select字段
    protected Connection connection;
    private boolean ignoreEntityCondition;
    private boolean ignoreNull;
    //选择
    private String select;
    private T data;

    public ArImpl(Dao dao, Transactions transactions) {
        this.transactions = transactions;
        this.driver = dao.sqlDriver();
        this.ds = dao.dataSource();
        this.dao = dao;
        this.builder = new SimpleSqlBuilder(driver);
    }

    protected Connection getConnection() throws SQLException {
        final Connection connection = this.connection;
        if (connection == null) {
            return this.connection = transactions.getConnection(ds);
        }
        return connection;
    }

    protected void releaseConnection() {
        transactions.releaseConnection(connection);
        connection = null;
    }


    public Ar entity(Entity entity) {
        this.entity = entity;
        return this;
    }

    public Ar<T> where(String key, Object value) {
        //  whereValues.add(value);
        builder.where(key, value);
        return this;
    }


    public Ar<T> where(String key, Symbol symbol, Object value) {
        builder.where(key, symbol, value);
        return this;
    }


    public Ar<T> where(Sql.Condition condition) {
        //builder.where(this, condition);
        return this;
    }


    public <E> Ar<T> whereIn(String key, Collection<E> values) {
        builder.whereIn(key, values);
        return this;
    }


    public Ar<T> whereNotNull(String key) {
        builder.whereNotNull(key);
        return this;
    }


    public Ar<T> whereNull(String key) {
        builder.whereNull(key);
        return this;
    }


    public Ar<T> like(String key, Sql.Like like, Object value) {
        builder.like(key, like, value);
        return this;
    }


    public Ar<T> orWhere(Sql.Condition condition) {
        //  builder.orWhere(this, condition);
        return this;
    }


    public Ar<T> orWhere(String key, Object value) {
        builder.orWhere(key, value);
        return this;
    }


    public Ar<T> orLike(String key, Sql.Like like, Object value) {
        builder.orLike(key, like, value);
        return this;
    }


    public Ar<T> exists(Select select) {
        return null;
    }


    public Ar<T> orderBy(String key, Sql.Sort sort) {
        builder.orderBy(key, sort);
        return this;
    }


    public Ar<T> groupBy(String key) {
        builder.groupBy(key);
        return this;
    }


    public Ar<T> having(String key, Symbol symbol, Object value) {
        builder.having(key, symbol, value);
        return this;
    }


    public Ar<T> union(SqlBuilder sqlBuilder) {
        return null;
    }


    public Ar<T> unionAll(SqlBuilder sqlBuilder) {
        return null;
    }


    public Ar<T> join(String table, String on) {
        return join(table, on, Sql.JoinType.INNER);
    }


    public Ar<T> join(String table, String on, Sql.JoinType type) {
        builder.join(table, on, type);
        return this;
    }


    public Ar<T> from(String table) {
        return null;
    }


    public Ar<T> select(String select) {
        if (StringUtils.equals("*", select) || StringUtils.isBlank(select)) {
            this.select = null;
        } else {
            this.select = select;
        }
        return this;
    }


    public Ar<T> select(Iterable<String> select) {
        return select(StringUtils.join(select, ","));
    }

    public int update(Object data) {
        builder.buildUpdate(entity, data, filter, ignoreNull, ignoreEntityCondition);
        PreparedStatement ps = null;
        try {
            Connection connection = getConnection();
            ps = connection.prepareStatement(builder.sql());
            builder.prepare(ps);
            return ps.executeUpdate();
        } catch (SQLException e) {
            throw new DaoException("update异常: " + builder.printSql(), e);
        } finally {
            ignoreEntityCondition = DEFAULT_IGNORE_ENTITY_CONDITION;
            ignoreNull = DEFAULT_IGNORE_NULL;
            Io.close(ps);
            releaseConnection();
        }
    }


    public Ar<T> set(T data) {
        this.data = data;
        return this;
    }


    public Ar<T> setExpression(String exp, Object... args) {
        return this;
    }


    public Ar<T> set(String key, Object value) {
        EntityField entityField = entity.tryToFind(key);
        if (data == null) {
            data = (T) entity.newInstance();
        }
        entityField.set(data, Caster.toType(value, entityField.getFieldType()));
        return this;
    }


    public List<T> find() {
        EntityField[] entityFields = builder.buildSelect(entity, select);
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Connection connection = getConnection();
            ps = connection.prepareStatement(builder.sql());
            builder.prepare(ps);
            rs = ps.executeQuery();
            //生成
            List<T> list = new ArrayList<>();
            while (rs.next()) {
                list.add((T) entity.fromResultSet(rs, entityFields));
            }
            return list;
        } catch (SQLException e) {
            throw new DaoException("query异常: " + builder.printSql(), e);
        } finally {
            Io.close(rs);
            Io.close(ps);
            clear();
        }
    }


    public List<T> query(String sql, Object... args) {
        return null;
    }


    /**
     * 获取某个统计值
     *
     * @param connection
     * @param all
     * @return
     */
    public int count(Connection connection, boolean all) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //分页
            builder.buildSelect(SqlBuilder.COUNT, entity.getTableName());
            ps = connection.prepareStatement(builder.sql());
            builder.prepare(ps);
            rs = ps.executeQuery();
            rs.next();
            return rs.getInt(1);
        } finally {
            Io.close(ps);
            Io.close(rs);
            builder.clear(all);
        }

    }


    public List<Object> values(String... keys) {
        assert (keys.length > 0);
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Connection connection = getConnection();
            //分页
            String str;
            if (keys.length == 1) {
                str = keys[0];
                keys = keys[0].split(",");
            } else {
                str = StringUtils.join(keys, ",");
            }
            builder.buildSelect(str, entity.getTableName());
            ps = connection.prepareStatement(builder.sql());
            builder.prepare(ps);
            rs = ps.executeQuery();
            rs.next();

            List<Object> list = new ArrayList<>(keys.length);
            for (int i = 1, c = keys.length; i <= c; ++i) {
                list.add(rs.getObject(i));
            }
            return list;
        } catch (SQLException e) {
            throw new DaoException("获取值失败" + builder.printSql(), e);
        } finally {
            Io.close(ps);
            Io.close(rs);
            clear();
        }
    }

    public <T> T execute(ConnectionExecutor executor) {
        return transactions.execute(ds, executor);
    }


    public <E> E value(String key, Class<E> typeOfE) {


        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Connection connection = getConnection();
            //分页
            builder.buildSelect(key, entity.getTableName());
            ps = connection.prepareStatement(builder.sql());
            builder.prepare(ps);
            rs = ps.executeQuery();
            rs.next();
            EntityField entityField = entity.tryToFind(key);
            ResultSetAdapter adapter;
            if (entityField != null) {
                adapter = typeOfE == entityField.getFieldType() ?
                        entityField.getResultSetAdapter() :
                        ResultSetAdapters.create(entityField.getColumnMeta().getColumnType(), typeOfE);
            } else {
                ResultSetMetaData meta = rs.getMetaData();
                int type = meta.getColumnType(1);
                Class<?> columnType = DaoUtils.dbType2JavaType(type);
                adapter = ResultSetAdapters.create(columnType, typeOfE);
            }
            return (E) adapter.getObject(rs, 1);
        } catch (SQLException e) {
            throw new DaoException("获取值失败" + builder.printSql(), e);
        } finally {
            Io.close(ps);
            Io.close(rs);
            clear();
        }
    }


    public int count() {
        try {
            Connection connection = getConnection();
            return count(connection, true);
        } catch (SQLException e) {
            throw new DaoException("count异常: " + builder.printSql(), e);
        } finally {
            releaseConnection();
        }
    }


    public Page<T> position(int position, int limit) {

        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Connection connection = getConnection();
            int total = count(connection, false);
            EntityField[] entityFields = builder.buildSelect(entity, select);
            builder.buildLimit(position, limit);
            ps = connection.prepareStatement(builder.sql());
            builder.prepare(ps);
            rs = ps.executeQuery();
            //生成
            List<T> list = new ArrayList<>();
            while (rs.next()) {
                list.add((T) entity.fromResultSet(rs, entityFields));
            }
            int page = DaoUtils.position2page(position, limit);
            return new Page<T>(list, page, limit, total);
        } catch (SQLException e) {
            throw new DaoException("query异常: " + builder.printSql(), e);
        } finally {
            Io.close(rs);
            Io.close(ps);
            clear();
        }
    }


    public Page<T> page(int page, int size) {
        if (page <= 0) page = 1;
        return position((page - 1) * size, size);
    }


    public List<T> limit(int position, int limit) {
        EntityField[] entityFields = builder.buildSelect(entity, select);
        builder.buildLimit(position, limit);
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Connection connection = getConnection();
            ps = connection.prepareStatement(builder.sql());
            builder.prepare(ps);
            rs = ps.executeQuery();
            //生成
            List<T> list = new ArrayList<>();
            while (rs.next()) {
                list.add((T) entity.fromResultSet(rs, entityFields));
            }
            return list;
        } catch (SQLException e) {
            throw new DaoException("query异常: " + builder.printSql(), e);
        } finally {
            Io.close(rs);
            Io.close(ps);
            clear();
        }
    }


    public T get(Object... values) {
        if (entity.getPrimaryKeys().length == 0) {
            throw new DaoException("本表" + entity.getTableName() + "没有主键，系统无法判断条件");
        }

        if (entity.getPrimaryKeys().length != values.length) {
            throw new DaoException(
                    "参数个数" + values.length + "与主键个数" + entity.getPrimaryKeys().length + "不符");
        }

        int index = 0;
        for (EntityField adapter : entity.getPrimaryKeys()) {
            builder.where(adapter.getColumn(), values[index++]);
        }

        return get();
    }

    public T get() {
        EntityField[] entityFields = builder.buildSelect(entity, select);
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            Connection connection = getConnection();
            ps = connection.prepareStatement(builder.sql());
            builder.prepare(ps);
            rs = ps.executeQuery();
            if (rs.next()) {
                return (T) entity.fromResultSet(rs, entityFields);
            }
            return null;
        } catch (SQLException e) {
            throw new DaoException("query异常: " + builder.printSql(), e);
        } finally {
            Io.close(rs);
            Io.close(ps);
            clear();
        }
    }

    private void clear() {
        releaseConnection();
        builder.clear(true);

        ignoreNull = DEFAULT_IGNORE_NULL;
        ignoreEntityCondition = DEFAULT_IGNORE_ENTITY_CONDITION;
    }


    public int executeUpdate(String sql, Object... args) {
        PreparedStatement ps = null;
        try {
            Connection connection = getConnection();
            ps = connection.prepareStatement(sql);
            for (int i = 1, c = args.length + 1; i < c; ++i) {
                ps.setObject(i, args[i]);
            }
            return ps.executeUpdate();
        } catch (SQLException e) {
            throw new DaoException("update异常: [" + SimpleSqlBuilder.getSql(sql, args) + "]", e);
        } finally {
            Io.close(ps);
            releaseConnection();
        }
    }


}
